Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru



tg-me.com/Python_Community_ru/2597
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru

BY Python Community


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/Python_Community_ru/2597

View MORE
Open in Telegram


Python Community Telegram | DID YOU KNOW?

Date: |

Can I mute a Telegram group?

In recent times, Telegram has gained a lot of popularity because of the controversy over WhatsApp’s new privacy policy. In January 2021, Telegram was the most downloaded app worldwide and crossed 500 million monthly active users. And with so many active users on the app, people might get messages in bulk from a group or a channel that can be a little irritating. So to get rid of the same, you can mute groups, chats, and channels on Telegram just like WhatsApp. You can mute notifications for one hour, eight hours, or two days, or you can disable notifications forever.

Telegram Gives Up On Crypto Blockchain Project

Durov said on his Telegram channel today that the two and a half year blockchain and crypto project has been put to sleep. Ironically, after leaving Russia because the government wanted his encryption keys to his social media firm, Durov’s cryptocurrency idea lost steam because of a U.S. court. “The technology we created allowed for an open, free, decentralized exchange of value and ideas. TON had the potential to revolutionize how people store and transfer funds and information,” he wrote on his channel. “Unfortunately, a U.S. court stopped TON from happening.”

Python Community from pl


Telegram Python Community
FROM USA